package biolabs.mcl

import biolabs.core.{Application, Placeholder}
import biolabs.mcl.syntax._

import java.lang.reflect.{Type, TypeVariable}

import scala.collection.Map

object SingleType {

	def apply(t: Type): SingleType =
		apply(t, Nil)

	def apply(t: Type, typeVarsInCreation: List[TypeVar]): SingleType =
		t match {
			case tv: TypeVariable[_] =>
				TypeVar(tv, typeVarsInCreation)
			case _ =>
				ActualType(t, typeVarsInCreation)
		}

	def analyze(ast: SingleTypeAST, model: ModelDefinition, resolvedTypeVars: Map[String, TypeVar], app: Application): AnalysisResult[SingleType] = {

		// check whether the type specification implicitly
		// (i.e. using _) refers to the type just being defined
		if(ast.id == TypeVar.IMPLICIT_VAR) {
			// make sure there are no type arguments provided
			if(!ast.typeArgs.isEmpty)
				return AnalysisError("Implicit type variable _ cannot take type arguments: " + ScalaCodeGen.refer(ast))

			return AnalysisSuccess(model.asType())
		}

		// check whether the type specification explicitly
		// refers to the type just being defined
		if(ast.id == model.name) {
			// check the number of type arguments
			val found = ast.typeArgs.length
			val expected = model.ast.typeParams.length
			if(found != expected)
				return AnalysisError("Wrong number of type arguments for " + ast.id + ". Found: " + found + ", expected: " + expected)

			// analyze type arguments
			val typeArgs = {
				val (suc, err) = ast.typeArgs map { MultiType.analyze(_, model, resolvedTypeVars, app) } partition { _.successful }
				if(!err.isEmpty)
					return AnalysisError(err map { _.getErrorMessage() } mkString "\n")
				suc map { _.getResult() }
			}

			return AnalysisSuccess(model.materialize(typeArgs))
		}

		// check whether the type specification refers to a type variable or an actual type
		model.ast.typeParams.collectFirst({ case tv: TypeParamAST if tv.name == ast.id => tv}) match {

			// reference to a type variable
			case Some(tv) =>
				// check that no type arguments are specified
				if(!ast.typeArgs.isEmpty)
					return AnalysisError("Type variable cannot take type arguments: " + ScalaCodeGen.refer(ast))

				// check whether the type variable is already resolved
				resolvedTypeVars.get(tv.name) match {
					case Some(v) =>
						AnalysisSuccess(v)

					case None =>
						resolvedTypeVars match {
							case _: scala.collection.immutable.Map[_, _] =>
								return AnalysisError("Unresolved type variable " + tv.name)
							case m: scala.collection.mutable.Map[String, TypeVar] =>
								TypeVar.analyze(tv, model, m, app)
						}
				}

			// try to analyze as an actual type
			case None =>
				ActualType.analyze(ast, model, resolvedTypeVars, app)
		}
	}

	private case class ReconstructionResult(
		result: MultiType,
		consumedClasses: Int,
		consumedTypeVariables: Int
	)

	def reconstruct(types: Array[Class[_]], typeParamRefs: Array[String], typeArguments: Map[String, MultiType]): MultiType = {
		val r = reconstruct(types, 0, typeParamRefs, 0, typeArguments)
		if(r.consumedClasses == types.length && r.consumedTypeVariables == typeParamRefs.length)
			return r.result
		if(r.consumedClasses < types.length)
			throw new IllegalArgumentException("Not all type arguments consumed (" + r.consumedClasses + " of " + types.length + ")")
		if(r.consumedTypeVariables < typeParamRefs.length)
			throw new IllegalArgumentException("Not all references to type parameters consumed (" + r.consumedTypeVariables + " of " + typeParamRefs.length + ")")
		throw new AssertionError("unreachable code")
	}

	private def reconstruct(types: Array[Class[_]], i: Int, typeParamRefs: Array[String], j: Int, typeArguments: Map[String, MultiType]): ReconstructionResult = {
		assert(i < types.length)
		val clazz = types(i)

		if(clazz == classOf[Placeholder]) {
			if(j >= typeParamRefs.length)
				throw new IndexOutOfBoundsException("Not enough references to type parameters")
			typeArguments.get(typeParamRefs(j)) match {
				case Some(t) => return ReconstructionResult(t, 1, 1)
				case None => throw new SemanticException("Reference to unknown type parameter: " + typeParamRefs(j))
			}
		}

		val nParams = clazz.getTypeParameters().length;
		val params = new Array[MultiType](nParams)
		var takenTypes = 1
		var takenRefs = 0
		for(k <- 0 until nParams) {
			if(i+takenTypes >= types.length)
				throw new IndexOutOfBoundsException("Not enough type parameters for " + clazz.getName())
			val r = reconstruct(types, i+takenTypes, typeParamRefs, j+takenRefs, typeArguments)
			params(k) = r.result
			takenTypes += r.consumedClasses
			takenRefs += r.consumedTypeVariables
		}
		val res = MultiType(ActualType(clazz, params.toList))
		ReconstructionResult(res, takenTypes, takenRefs)
	}
}

/**
 * Abstract ancestor for representations of common Java types.
 *
 * @author Tomas Mikula
 */
abstract class SingleType {

	/**
	 * Determines whether this type is a descendant
	 * of the raw type {@code ancestor}.
	 * @param ancestor
	 * @return
	 */
	def isDescendantOf(ancestor: Class[_]): Boolean

	/**
	 * Resolves actual type parameters of the given supertype.
	 * @param ancestor any supertype of this type.
	 * @return actual type arguments of {@code ancestor}
	 * as they are materialized by this type.
	 * @throws IllegalArgumentException if this type
	 * is not a descendant of {@code ancestor}.
	 */
	def getTypeArgumentsOf(ancestor: Class[_]): Seq[MultiType]

	/**
	 * Determines the assignability, with respect to type arguments.
	 */
	def isAssignableFrom(sub: SingleType): Boolean

	/**
	 * Determines the assignability, with respect to type arguments.
	 */
	final def isAssignableFrom(sub: MultiType): Boolean =
		sub.types exists { isAssignableFrom(_) }

	/**
	 * Lists the methods that return delegate candidates for the given
	 * interaction.
	 *
	 * <p>A delegate candidate for interaction {@code A} is a method
	 * annotated {@code @Delegate(action=B)}, where {@code B <: A}, that
	 * is not overridden by a more general delegate method, in the following
	 * sense.</p>
	 *
	 * <p>If this type declares a delegate for {@code B} and a delegate
	 * for {@code C}, where {@code C <: B <: interaction}, then the
	 * delegate for {@code C} is not returned as a delegate candidate
	 * for {@code interaction} (provided the inequality {@code C <: B}
	 * is strict).</p>
	 *
	 * <p>If this type <em>inherits</em> a delegate for
	 * {@code C <: interaction} and <em>declares</em> a delegate for
	 * {@code B} where {@code C <: B <: interaction}, then the inherited
	 * delegate for {@code C} is not returned as a delegate candidate for
	 * {code interaction} (this time, the {@code C <: B} inequality can
	 * be non-strict).</p>
	 *
	 * <p>If this type inherits a candidate for {@code B <: interaction}
	 * and a candidate for {@code C <: interaction} and does not declare
	 * a more general delegate, both inherited delegate candidates are
	 * returned.</p>
	 *
	 * <p>Delegate candidates of supertypes are defined recursively.</p>
	 */
	def getDelegateCandidates(interaction: ActualType): List[DelegateMethod]

	// enforce custom implementation of hashCode and equals methods
	override def hashCode(): Int = throw new AbstractMethodError("hashCode() not overriden in " + this.getClass())
	override def equals(that: Any): Boolean = throw new AbstractMethodError("equals() not overriden in " + this.getClass())

	def isEqualTo(that: SingleType, comparing: List[(TypeVar, TypeVar)]): Boolean
}